#include "image.h"

namespace SkyID
{

//- ImagePNG ------------------------------------------------------------------

ImagePNG::ImagePNG()
{
	//
}

ImagePNG::ImagePNG(Reader *reader)
{
	setValid(open(reader));
}

bool ImagePNG::open(Reader* reader)
{
	LOG_ASSERT(reader && reader->isValid());

	png_byte header[8] = {0};
	reader->read(header, 8);

	if (png_sig_cmp(header, 0, 8))
	{
		LOG_MSG("ImagePNG: wrong file signature");
		return false;
	}

	png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, pngError, 0);
	if (!pngPtr)
	{
		LOG_MSG("ImagePNG: cannot create png read struct");
		return false;
	}

	png_infop infoPtr = png_create_info_struct(pngPtr);
	if (!infoPtr) 
	{
		png_destroy_read_struct(&pngPtr, 0, 0);
		LOG_MSG("ImagePNG: cannot create png info struct");
		return false;
	}

	if (setjmp(png_jmpbuf(pngPtr)))
	{
		png_destroy_read_struct(&pngPtr, &infoPtr, 0);
		LOG_MSG("ImagePNG: cannot set png jmp buffer");
		return false;
	}

	png_set_read_fn(pngPtr, reader, pngRead);
	png_set_sig_bytes(pngPtr, 8);
	png_read_info(pngPtr, infoPtr);

	png_uint_32 width = 0, height = 0;
	int bpp = 0;
	int colorType = 0;

	// get image header for the first time
	png_get_IHDR(pngPtr, infoPtr, &width, &height, &bpp, &colorType, 0, 0, 0);

	// if image has palette, convert it to RGB
	if (colorType == PNG_COLOR_TYPE_PALETTE)
		png_set_palette_to_rgb(pngPtr);

	// if we have here strange bitmap format, do convert
	if (bpp < 8)
	{
		if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
			png_set_gray_1_2_4_to_8(pngPtr);
		else
			png_set_packing(pngPtr);
	}

	// if we have alpha information, convert PNG's alpha layer to alpha channel (RGBA)
	if (png_get_valid(pngPtr, infoPtr, PNG_INFO_tRNS))
		png_set_tRNS_to_alpha(pngPtr);

	if (bpp == 16)
		png_set_strip_16(pngPtr);

	if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
		png_set_gray_to_rgb(pngPtr);

	// detect alpha
	ImageFormat format = colorType==PNG_COLOR_TYPE_RGB_ALPHA ? FORMAT_RGBA : FORMAT_RGB;

	// update internal info
	png_read_update_info(pngPtr, infoPtr);
	// update our copy of image header info
	png_get_IHDR(pngPtr, infoPtr, &width, &height, &bpp, &colorType, 0, 0, 0);

	// allocate memory for image data
	createImage(width, height, format);

	png_bytep* rowPointers = new png_bytep[mHeight];
	LOG_ASSERT(rowPointers);

	uint32 stride = mWidth * mFormat;
	for (uint32 i = 0; i < mHeight; ++i)
		rowPointers[i] = (png_bytep)mData + i*stride;

	png_read_image(pngPtr, rowPointers);
	png_read_end(pngPtr, 0);

	delete [] rowPointers;

	png_destroy_read_struct(&pngPtr, &infoPtr, 0);

	return true;
}

// libpng error handler
void ImagePNG::pngError(png_structp png_ptr, png_const_charp msg)
{
    LOG_MSG("ImagePNG: %s", msg);
    longjmp(png_ptr->jmpbuf, 1);
}

// libpng custom reader
void PNGAPI ImagePNG::pngRead(png_structp png_ptr, png_bytep data, png_size_t length)
{
	Reader *reader = reinterpret_cast<Reader*>(png_ptr->io_ptr);
	LOG_ASSERT(reader);
	png_size_t check;
	check = (png_size_t)reader->read((void*)data, length);
	if (check != length)
		png_error(png_ptr, "Read Error");
}

} // namespace SkyID
